import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.json.simple.JSONObject;

/**
 * This class is responsible for taking the AST built by the parser and building meaningful results from it
 * This class also packages the data into JSON Objects and Maps for the Visualization builder to use
 */
public class Interpreter {
	
	public Interpreter() {
		
	}
	 
	/**
	 * Interprets an abstract android class by counting the characters inside its methods
	 * @param abstractAndroidClass The abstract android class to be interpreted
	 * @param prevVersion The previous version of the android class used to calculate the relative change
	 * @return A Map with the key as a JSONObject and the value as a list of JSONObjects
	 */
	public Map<JSONObject, List<JSONObject>> interpret(AbstractAndroidClass abstractAndroidClass, List <JSONObject> prevVersion) {
		Map <JSONObject, List<JSONObject>> classMap = new HashMap <JSONObject, List<JSONObject>> ();
		List <JSONObject> valueList = new ArrayList <JSONObject> ();
		int classCharCount = sumNonMethodCharCount(abstractAndroidClass);
		
		for(AbstractAndroidMethod abstractAndroidMethod : abstractAndroidClass.getAbstractAndroidMethods()) {
			JSONObject jsonObject = new JSONObject();
			CharCountContainer countContainer = interpAst(abstractAndroidMethod.getAstList());
			int relativeCharCount = calculateRelativeCharacters(countContainer.getTotalCharCount(), prevVersion, abstractAndroidMethod.getName());
			
			jsonObject.put("Name", abstractAndroidMethod.getName());
			jsonObject.put("If Count", countContainer.getIfCharCount());
			jsonObject.put("Loop Count", countContainer.getLoopCharCount());
			jsonObject.put("Other Count", countContainer.getOtherCharCount());
			jsonObject.put("Absolute Chars", countContainer.getTotalCharCount());
			jsonObject.put("Relative Chars", relativeCharCount);
			
			classCharCount += countContainer.getTotalCharCount();
			valueList.add(jsonObject);
		}
		JSONObject keyJSONObject = new JSONObject(); 
		keyJSONObject.put("Class Name", abstractAndroidClass.getName());
		keyJSONObject.put("Class Count", classCharCount);
		classMap.put(keyJSONObject, valueList);
		return classMap;
	}
	
	/**
	 * Interprets an abstract syntax tree and places them inside of a container
	 * @param astList The AST to be interpreted
	 * @return A container that contains the character counts from the different types of abstract syntax 
	 */
	private CharCountContainer interpAst(List <ExprS> astList) {
		CharCountContainer currContainer = new CharCountContainer();
		for (ExprS ast : astList) {
			CharCountContainer nextContainer = new CharCountContainer();
			if (ast instanceof IfS) {
				nextContainer = interpIfS(currContainer, ast);
			}
			else if (ast instanceof LoopS) {
				nextContainer = interpLoopS(currContainer, ast);
			}
			else {
				currContainer.setOtherCharCount(ast.getCode().length() + currContainer.getOtherCharCount());
			}
			
			if(nextContainer != null) {
				currContainer.combine(nextContainer);
			}
		}
		return currContainer;
	}
	
	/**
	 * Interprets an IfS statement and places the results in a container. Then, interprets the body of the IfS
	 * @param currContainer The container that will hold the results
	 * @param ast The IfS statement to be evaluated
	 * @return A container that contains the results.
	 */
	private CharCountContainer interpIfS(CharCountContainer currContainer,ExprS ast) {
		IfS ifAst = (IfS) ast;
		currContainer.setIfCharCount(ifAst.getCode().length() + currContainer.getIfCharCount());
		CharCountContainer nextContainer = interpThen(ifAst.getThen());
		return nextContainer;
	}
	
	/**
	 * Interprets an LoopS statement and places the results in a container. Then, interprets the body of the LoopS
	 * @param currContainer The container that will hold the results
	 * @param ast The LoopS statement to be evaluated
	 * @return A container that contains the results.
	 */
	private CharCountContainer interpLoopS(CharCountContainer currContainer,ExprS ast) {
		LoopS loopAst = (LoopS) ast;
		currContainer.setLoopCharCount(loopAst.getCode().length() + currContainer.getLoopCharCount());
		CharCountContainer nextContainer = interpLoopBody(loopAst.getBody());
		return nextContainer;
	}
	
	/**
	 * Interprets the then part of an IfS statement and places the results in a container.
	 * Then, interpret the expressions that follow the IfS
	 * @param astList A list of ASTs remaining to be interpreted
	 * @return A container that contains the results.
	 */
	private CharCountContainer interpThen(List <ExprS> astList) {
		CharCountContainer currContainer = new CharCountContainer();
		if(astList != null && astList.size() != 0) {
			if(astList.get(0) instanceof IfS || astList.get(0) instanceof LoopS) {
				currContainer.combine(interpAst(astList));
			}
			else {
				currContainer.setIfCharCount(astList.get(0).getCode().length());
				if(astList.size() > 1) {
					astList.remove(0);
					currContainer.combine(interpAst(astList));
				}
			}
		}
		return currContainer;
	}
	
	/**
	 * Interprets the body part of an LoopS statement and places the results in a container.
	 * Then, interpret the expressions that follow the LoopS
	 * @param astList A list of ASTs remaining to be interpreted
	 * @return A container that contains the results.
	 */
	private CharCountContainer interpLoopBody(List <ExprS> astList) {
		CharCountContainer currContainer = new CharCountContainer();
		if(astList != null && astList.size() != 0) {
			if(astList.get(0) instanceof IfS || astList.get(0) instanceof LoopS) {
				currContainer.combine(interpAst(astList));
			}
			else {
				currContainer.setLoopCharCount(astList.get(0).getCode().length());
				if(astList.size() > 1) {
					astList.remove(0);
					currContainer.combine(interpAst(astList));
				}
			}
		}
		return currContainer;
	}
	
	/**
	 * Gets the character count of all the non methods in the class
	 * @param abstractAndroidClass The abstract android class that contains the non methods
	 * @return The character count of all the non methods in the class
	 */
	private int sumNonMethodCharCount (AbstractAndroidClass abstractAndroidClass) {
		int nonMethodCharCount = 0;
		for (AbstractNonMethod nonMethod : abstractAndroidClass.getNonMethods()) {
			nonMethodCharCount += nonMethod.getCode().length();
		}
		return nonMethodCharCount;
	}
	
	/**
	 * Calcuates the relative character count from the previous android revision
	 * @param absoluteChars The absolute number of characters in the current version
	 * @param prevVersion A list containing all the previous versions methods of the class 
	 * @param methodName The method name that is used to find the previous version
	 * @return The relative character count of the method in the current android revision to the previous
	 */
	private int calculateRelativeCharacters(int absoluteChars, List<JSONObject> prevVersion, String methodName) {
		if (prevVersion == null)
			return 0;
		Integer relChars = 0;
		for (JSONObject method : prevVersion) {
			if(method.get("Name").equals(methodName)) {
				relChars = Math.abs(absoluteChars - ((Integer) method.get("Absolute Chars")));
				break;
			}
		}
		return relChars;
	}

}
